package org.agile2soft.nexoma.tapestry.services;

import java.io.IOException;

import org.agile2soft.nexoma.core.controllers.ProfilesController;
import org.agile2soft.nexoma.core.controllers.ServiceController;
import org.agile2soft.nexoma.core.controllers.ServiceOptionController;
import org.agile2soft.nexoma.core.controllers.ServicesGroupController;
import org.agile2soft.nexoma.core.controllers.UserController;
import org.agile2soft.nexoma.core.entities.InternalUser;
import org.agile2soft.nexoma.core.entities.Profile;
import org.agile2soft.nexoma.core.entities.Service;
import org.agile2soft.nexoma.core.entities.ServiceOption;
import org.agile2soft.nexoma.core.entities.ServicesGroup;
import org.agile2soft.nexoma.tapestry.encoder.ProfileEncoder;
import org.agile2soft.nexoma.tapestry.encoder.ServiceEncoder;
import org.agile2soft.nexoma.tapestry.encoder.ServiceOptionEncoder;
import org.agile2soft.nexoma.tapestry.encoder.ServicesGroupEncoder;
import org.agile2soft.nexoma.tapestry.encoder.UserEncoder;
import org.agile2soft.nexoma.tapestry.model.ApplicationContainer;
import org.agile2soft.nexoma.tapestry.services.security.MyUserDetailsService;
import org.apache.tapestry5.*;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestFilter;
import org.apache.tapestry5.services.RequestHandler;
import org.apache.tapestry5.services.Response;
import org.slf4j.Logger;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.AuthenticationProvider;
import org.springframework.security.providers.dao.SaltSource;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.userdetails.UserDetailsService;

import br.com.arsmachina.controller.Controller;
import br.com.arsmachina.tapestrycrud.selectmodel.DefaultSingleTypeSelectModelFactory;
import br.com.arsmachina.tapestrycrud.selectmodel.SingleTypeSelectModelFactory;

/**
 * This module is automatically included as part of the Tapestry IoC Registry, it's a good place to
 * configure and extend Tapestry, or to place your own service definitions.
 */
public class AppModule
{

    public static void bind(ServiceBinder binder)
    {
        binder.bind(SupportedLocales.class,SupportedLocalesImpl.class);
        binder.bind(InternalUser.class);
        binder.bind(ProfileEncoder.class);
        binder.bind(ServicesGroupEncoder.class);
        binder.bind(ServiceEncoder.class);
        binder.bind(ServiceOption.class);
    }

    public void contributeControllerSource(MappedConfiguration<Class,Controller>contributions,
            UserController userController,
            ProfilesController profilesController,
            ServicesGroupController servicesGroupController,
            ServiceController serviceController,
            ServiceOptionController serviceOptionController ) {
        contributions.add(InternalUser.class, userController);
        contributions.add(Profile.class, profilesController);
        contributions.add(ServicesGroup.class, servicesGroupController);
        contributions.add(Service.class,serviceController);
        contributions.add(ServiceOption.class,serviceOptionController);
    }

    public void contributeSelectModelFactory(
            MappedConfiguration<Class, SingleTypeSelectModelFactory> contributions,
            UserController userController, UserEncoder userEncoder,
            ProfilesController profilesController, ProfileEncoder profileEncoder,
            ServicesGroupController servicesGroupController, ServicesGroupEncoder servicesGroupEncoder,
            ServiceController serviceController, ServiceEncoder serviceEncoder,
            ServiceOptionController serviceOptionController, ServiceOptionEncoder serviceOptionEncoder) {

        DefaultSingleTypeSelectModelFactory<InternalUser>userSMF =
            new DefaultSingleTypeSelectModelFactory<InternalUser>(userController,userEncoder);
        contributions.add(InternalUser.class,userSMF);

        DefaultSingleTypeSelectModelFactory<Profile>profileSMF =
            new DefaultSingleTypeSelectModelFactory<Profile>(profilesController,profileEncoder);
        contributions.add(Profile.class,profileSMF);

        DefaultSingleTypeSelectModelFactory<ServicesGroup>servicesGroupSMF =
            new DefaultSingleTypeSelectModelFactory<ServicesGroup>(servicesGroupController,servicesGroupEncoder);
        contributions.add(ServicesGroup.class,servicesGroupSMF);

        DefaultSingleTypeSelectModelFactory<Service>serviceSMF =
            new DefaultSingleTypeSelectModelFactory<Service>(serviceController,serviceEncoder);
        contributions.add(Service.class,serviceSMF);

        DefaultSingleTypeSelectModelFactory<ServiceOption>serviceOptionSMF =
            new DefaultSingleTypeSelectModelFactory<ServiceOption>(serviceOptionController,serviceOptionEncoder);
        contributions.add(ServiceOption.class,serviceOptionSMF);
    }

    @Scope(value=ScopeConstants.PERTHREAD)
    public Authentication buildAuthentication () {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    @EagerLoad
    public static ApplicationContainer buildApplicationContainer(final ServicesGroupController serviceGroupController,
                                                                 final ServiceController serviceController,
                                                                 final ServiceOptionController serviceOptionController) {
        // CLEAN DATABASE
        for ( ServiceOption serviceOption : serviceOptionController.findAll() ) {
            serviceOptionController.delete(serviceOption);
        }
        for ( Service service : serviceController.findAll() ) {
            serviceController.delete(service);
        }
        for ( ServicesGroup serviceGroup : serviceGroupController.findAll() ) {
            serviceGroupController.delete(serviceGroup);
        }

        // INSERT MOCK DATA
        ServicesGroup portalGroup    = new ServicesGroup("Index","portal");
        ServicesGroup adminGroup     = new ServicesGroup("Management","admin");
//        ServiceGroup expedientGroup = new ServiceGroup("Expedients","expedient");
//        ServiceGroup documentGroup  = new ServiceGroup("Documents","document");

        serviceGroupController.save(portalGroup);
        serviceGroupController.save(adminGroup);
//        serviceGroupController.save(expedientGroup);
//        serviceGroupController.save(documentGroup);

        Service userAdminService     = new Service("Users","admin/Users","Users Management",true,false,"1",adminGroup);
        Service serviceAdminService  = new Service("Services","admin/Services","Services Management",true,false,"1",adminGroup);
        Service profilesAdminService = new Service("Profiles","admin/Profiles","Profiles Management",true,false,"1",adminGroup);

//        Service expedientTypeService  = new Service("Types",true,"expedient/Types","Expedient Type Management",true,false,"1",expedientGroup);
//        Service expedientPhaseService = new Service("Phases",true,"expedient/Phases","Expedient Phases Management",true,false,"1",expedientGroup);
//        Service expedientService      = new Service("Expedients",true,"expedient/List","Expedient Management",true,false,"1",expedientGroup);

        serviceController.save(userAdminService);
        serviceController.save(serviceAdminService);
        serviceController.save(profilesAdminService);

//        serviceController.save(expedientTypeService);
//        serviceController.save(expedientPhaseService);
//        serviceController.save(expedientService);

        ServiceOption userListOption = new ServiceOption("List Users","admin/Users/List",userAdminService);
        ServiceOption userEditOption = new ServiceOption("Create User","admin/Users/Edit",userAdminService);

        ServiceOption profileListOption = new ServiceOption("List Profiles","admin/Profiles/List",profilesAdminService);
        ServiceOption profileEditOption = new ServiceOption("Create Profile","admin/Profiles/Edit",profilesAdminService);

        ServiceOption servicesGroupListOption = new ServiceOption("List Groups","admin/Services/GroupList",serviceAdminService);
        ServiceOption servicesGroupEditOption = new ServiceOption("Create Group","admin/Services/GroupEdit",serviceAdminService);

        ServiceOption servicesListOption = new ServiceOption("List Services","admin/Services/List",serviceAdminService);
        ServiceOption servicesEditOption = new ServiceOption("Create Service","admin/Services/Edit",serviceAdminService);

        ServiceOption servicesOptionListOption = new ServiceOption("List Options","admin/Services/OptionList",serviceAdminService);
        ServiceOption servicesOptionEditOption = new ServiceOption("Create Option","admin/Services/OptionEdit",serviceAdminService);

//        ServiceOption expedientTypeListOption = new ServiceOption("List Types","expedient/TypesList","listExpedientTypes",expedientTypeService);
//        ServiceOption expedientPhaseListOption = new ServiceOption("List Phases","expedient/PhasesList","listPhases",expedientPhaseService);
//        ServiceOption expedientListOption = new ServiceOption("List Expedients","expedient/List","listExpedients",expedientService);
//        ServiceOption expedientOpenOption = new ServiceOption("Open Expedient","expedient/Open","openExpedient",expedientService);

        serviceOptionController.save(userListOption);
        serviceOptionController.save(userEditOption);

        serviceOptionController.save(profileListOption);
        serviceOptionController.save(profileEditOption);

        serviceOptionController.save(servicesGroupListOption);
        serviceOptionController.save(servicesGroupEditOption);
        serviceOptionController.save(servicesListOption);
        serviceOptionController.save(servicesEditOption);
        serviceOptionController.save(servicesOptionListOption);
        serviceOptionController.save(servicesOptionEditOption);


//        serviceOptionController.save(expedientTypeListOption);
//        serviceOptionController.save(expedientPhaseListOption);
//        serviceOptionController.save(expedientListOption);
//        serviceOptionController.save(expedientOpenOption);

        ApplicationContainer appContainer = new ApplicationContainer();

        appContainer.setServicesGroups(serviceGroupController.findAll());

        return appContainer;
    }

    public static UserDetailsService buildUserDetailsService(
            @Inject
            PasswordEncoder encoder,
            @Inject
            SaltSource salt ) {
        return new MyUserDetailsService( encoder, salt);
    }

    public static void contributeProviderManager( OrderedConfiguration<AuthenticationProvider> configuration
                                                 , @InjectService( "DaoAuthenticationProvider" )
                                                    AuthenticationProvider daoAuthenticationProvider ) {

        configuration.add( "daoAuthenticationProvider", daoAuthenticationProvider );
    }

    public static void contributeApplicationDefaults(
            MappedConfiguration<String, String> configuration)
    {
        configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en,es,fr,de,jp");
        configuration.add(SymbolConstants.PRODUCTION_MODE, "false");
        configuration.add(SymbolConstants.APPLICATION_VERSION, "0.0.1-SNAPSHOT");
        // TAPESTRY-SPRING-SECURITY Configuration
        configuration.add( "tapestry.default-cookie-max-age", "31536000" );
        /* The internal check url used by Spring Security, Default to "/j_acegi_security_check". */
        configuration.add("spring-security.check.url","/j_acegi_security_check");
        /* Url redirected to when fails to login. Defaults to "/loginfailed". */
        configuration.add("spring-security.failure.url","/loginfailed");
        /* Url redirected to after a successful url ifr there is no "secured url" stored on session. Defaults to "/". */
        configuration.add("spring-security.target.url","/login");
        /* Url redirected to after a successful logout. Defaults to "/". */
        configuration.add("spring-security.afterlogout.url", "/logout");

        /*
         * Defaults to "". If set to other than empty, the request dispatcher will "forward" to this specified error page view. From Acegi
         * documentation: The error page to use. Must begin with a "/" and is interpreted relative to the current context root
        configuration.add("spring-security.accessDenied.url","/forbidden");
        /* Key used by the remember me services. Defauls to "REMEMBERMEKEY".*/
        configuration.add("spring-security.remenberme.key","REMENBERMEKEY");
        /* Url redirected to when trying to use a secured class and/or method. Defaults to "/login" */
        configuration.add("spring-security.loginform.url,", "/forbidden");
        /* Used to indicate if redirect to loginform should also switch to secure channel. */
        configuration.add("spring-security.force.ssl.login","false");
        /* Key used by the anonymous service. Defaults to "acegi_anonymous" */
        configuration.add( "spring-security.anonymous.key", "acegi_citizen" );
        /* Attributes set on anonymous uses. Defaults to "anonymous,ROLE_ANONYMOUS". */
        configuration.add( "spring-security.anonymous.attribute","citizen,ROLE_CITIZEN" );
        /* Salt the password is salted with. Defaults to "DEADBEEF" */
        configuration.add( "spring-security.password.salt", "DEADBEEF" );
    }

//    public static void contributeJavascriptStack(
//            @Path("context:resources/js/jquery.js") Asset jquery,
//            Configuration<Asset> stack){
//        stack.add(jquery);
//    }


    /**
     * This is a service definition, the service will be named "TimingFilter".
     */
    public RequestFilter buildTimingFilter(final Logger log)
    {
        return new RequestFilter()
        {
            public boolean service(Request request, Response response, RequestHandler handler)
                    throws IOException
            {
                long startTime = System.currentTimeMillis();

                try
                {
                    return handler.service(request, response);
                }
                finally
                {
                    long elapsed = System.currentTimeMillis() - startTime;

                    log.info(String.format("Request time: %d ms", elapsed));
                }
            }
        };
    }

    /**
     * This is a contribution to the RequestHandler service configuration. This is how we extend
     * Tapestry using the timing filter. A common use for this kind of filter is transaction
     * management or security. The @Local annotation selects the desired service by type, but only
     * from the same module.  Without @Local, there would be an error due to the other service(s)
     * that implement RequestFilter (defined in other modules).
     */
    public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
            @Local
            RequestFilter filter)
    {
        // Each contribution to an ordered configuration has a name, When necessary, you may
        // set constraints to precisely control the invocation order of the contributed filter
        // within the pipeline.

        configuration.add("Timing", filter);
    }

}
